import Tone from "../core/Tone";
import "../core/Transport";
import "../signal/Signal";
import "../type/TransportTime";

/**
 * @class Tone.TransportTimelineSignal extends Tone.Signal, but adds the ability to synchronize the signal to the signal to the Tone.Transport
 * @extends {Tone.Signal}
 */
Tone.TransportTimelineSignal = function(){
	Tone.Signal.apply(this, arguments);

	/**
	 * The real signal output
	 * @type {Tone.Signal}
	 * @private
	 */
	this.output = this._outputSig = new Tone.Signal(this._initialValue);

	/**
	 * Keep track of the last value. (small optimization)
	 * @private
	 * @type {Number}
	 */
	this._lastVal = this.value;

	/**
	 * The event id of the tick update loop
	 * @private
	 * @type {Number}
	 */
	this._synced = Tone.Transport.scheduleRepeat(this._onTick.bind(this), "1i");

	/**
	 * A bound version of the anchor value methods
	 * @type {Function}
	 * @private
	 */
	this._bindAnchorValue = this._anchorValue.bind(this);
	Tone.Transport.on("start stop pause", this._bindAnchorValue);

	this._events.memory = Infinity;
};

Tone.extend(Tone.TransportTimelineSignal, Tone.Signal);

/**
 * Callback which is invoked every tick.
 * @private
 * @param  {Number} time
 * @return {Tone.TransportTimelineSignal}      this
 */
Tone.TransportTimelineSignal.prototype._onTick = function(time){
	var val = this.getValueAtTime(Tone.Transport.seconds);
	if (this._lastVal !== val){
		this._lastVal = val;
		//approximate ramp curves with linear ramps
		this._outputSig.linearRampToValueAtTime(val, time);
	}
};

/**
 * Anchor the value at the start and stop of the Transport
 * @param  {Number} time The time of the event
 * @return {Tone.TransportTimelineSignal}      this
 * @private
 */
Tone.TransportTimelineSignal.prototype._anchorValue = function(time){
	var val = this.getValueAtTime(Tone.Transport.seconds);
	this._lastVal = val;
	this._outputSig.cancelScheduledValues(time);
	this._outputSig.setValueAtTime(val, time);
	return this;
};

/**
 *  Get the scheduled value at the given time. This will
 *  return the unconverted (raw) value.
 *  @param  {TransportTime}  time  The time in seconds.
 *  @return  {Number}  The scheduled value at the given time.
 */
Tone.TransportTimelineSignal.prototype.getValueAtTime = function(time){
	time = Tone.TransportTime(time);
	return Tone.Signal.prototype.getValueAtTime.call(this, time);
};

/**
 * Set the output of the signal at the given time
 * @param  {Number} value The value to change to at the given time
 * @param  {TransportTime} time  The time to change the signal
 * @return {Tone.TransportTimelineSignal}       this
 */
Tone.TransportTimelineSignal.prototype.setValueAtTime = function(value, time){
	time = Tone.TransportTime(time);
	Tone.Signal.prototype.setValueAtTime.call(this, value, time);
	return this;
};

/**
 * Linear ramp to the given value from the previous scheduled point to the given value
 * @param  {Number} value The value to change to at the given time
 * @param  {TransportTime} time  The time to change the signal
 * @return {Tone.TransportTimelineSignal}       this
 */
Tone.TransportTimelineSignal.prototype.linearRampToValueAtTime = function(value, time){
	time = Tone.TransportTime(time);
	Tone.Signal.prototype.linearRampToValueAtTime.call(this, value, time);
	return this;
};

/**
 * Exponential ramp to the given value from the previous scheduled point to the given value
 * @param  {Number} value The value to change to at the given time
 * @param  {TransportTime} time  The time to change the signal
 * @return {Tone.TransportTimelineSignal}       this
 */
Tone.TransportTimelineSignal.prototype.exponentialRampToValueAtTime = function(value, time){
	time = Tone.TransportTime(time);
	Tone.Signal.prototype.exponentialRampToValueAtTime.call(this, value, time);
	return this;
};

/**
 *  Start exponentially approaching the target value at the given time with
 *  a rate having the given time constant.
 *  @param {number} value
 *  @param {TransportTime} startTime
 *  @param {number} timeConstant
 * @return {Tone.TransportTimelineSignal}       this
 */
Tone.TransportTimelineSignal.prototype.setTargetAtTime = function(value, startTime, timeConstant){
	startTime = Tone.TransportTime(startTime);
	Tone.Signal.prototype.setTargetAtTime.call(this, value, startTime, timeConstant);
	return this;
};

/**
 *  Cancels all scheduled parameter changes with times greater than or
 *  equal to startTime.
 *  @param  {TransportTime} startTime
 *  @returns {Tone.Param} this
 */
Tone.TransportTimelineSignal.prototype.cancelScheduledValues = function(startTime){
	startTime = Tone.TransportTime(startTime);
	Tone.Signal.prototype.cancelScheduledValues.call(this, startTime);
	return this;
};

/**
 *  Set an array of arbitrary values starting at the given time for the given duration.
 *  @param {Float32Array} values
 *  @param {Time} startTime
 *  @param {Time} duration
 *  @param {NormalRange} [scaling=1] If the values in the curve should be scaled by some value
 *  @returns {Tone.Signal} this
 */
Tone.TransportTimelineSignal.prototype.setValueCurveAtTime = function(values, startTime, duration, scaling){
	startTime = Tone.TransportTime(startTime);
	duration = Tone.TransportTime(duration);
	Tone.Signal.prototype.setValueCurveAtTime.call(this, values, startTime, duration, scaling);
	return this;
};

/**
 *  This is similar to [cancelScheduledValues](#cancelScheduledValues) except
 *  it holds the automated value at time until the next automated event.
 *  @param  {Time} time
 *  @returns {Tone.TransportTimelineSignal} this
 */
Tone.TransportTimelineSignal.prototype.cancelAndHoldAtTime = function(time){
	return Tone.Signal.prototype.cancelAndHoldAtTime.call(this, Tone.TransportTime(time));
};

/**
 * Dispose and disconnect
 * @return {Tone.TransportTimelineSignal} this
 */
Tone.TransportTimelineSignal.prototype.dispose = function(){
	Tone.Transport.clear(this._synced);
	Tone.Transport.off("start stop pause", this._syncedCallback);
	this._events.cancel(0);
	Tone.Signal.prototype.dispose.call(this);
	this._outputSig.dispose();
	this._outputSig = null;
};

export default Tone.TransportTimelineSignal;

